<template>
	<div :class="clientHeight==0?'':'listtop'" ref="listtop">
		<div class="list" @scroll="scroll" ref="list" 
		@touchstart="touchstart"
		@touchmove="touchmove"
		@touchend="touchend">
	       <div :style="'height:' + scrollTop + 'px'" style="position: relative;">
			   <view v-if="triggeredType==0" class="refresherm">下拉加载</view>
			   <view v-if="triggeredType==1" class="refresherm">松开刷新</view>
			   <view v-if="triggeredType==2" class="refresherm cartoon">
			   	 <loading></loading>
			   </view>
			  <view v-if="triggeredType==3" class="refresherm">刷新成功</view>
		   </div>
			<div :style="'height:' + height + 'px'"></div>
			<div v-if="list2 && list2.length > 0">
				<slot :list="list2"></slot>
			</div>
		</div>
	   <div class="_backtop" v-if="isBackTop" @click.stop="toTop" >
		<view v-if="scrollTopx > 100">
			 <transition name="fade" appear>
				<div class="top" >
					<image src="./top.png"></image>
				</div>
			 </transition>
		</view>
	</div>
	</div>
</template>
<script>
	/**
	 * 虚拟列表-解决大数据列表操作问题
	 * 2023-7-12  jp
	 * 
	 * ****** 参数 ******
	 * lazy     是否懒加载，两种模式一种懒加载（数据量在5000以下），虚拟dom（大数据模式）
	 * pageSizes  加载分页默认20
	 * data        数据列表
	 * 
	 * ****** 回调 ******
	 * @operate 操作数据成功之后的回调
	 * 
	 * ******* 事件 ********
	 * getAllData  获取所有数据
	 * setValData(key1,val1,key2,val2)  把数据key1==val1的数据的key2改为val2
	 * deleteData(key, val)  删除对应元素
	 * getData(key, val)  通过 key 查找起2对应val的数据
	 * setData(key, val)   改变全部数据，通过key来改变数据为val
	 * search(key, val , vague)   搜索操作，通过key来搜索val的数据 vague 是否开启模糊搜索
	 */
	let time = null;
	import loading from './loading.vue'
	export default {
		components: {
			loading
		},
		data() {
			return {
				clientHeight: 100,
				listPage: [],
				list2: [],
				list: [],
				copyList: [],
				page: 0,
				height: 0,
				heightList: [0],
				scrollClientY:0,
				scrollTop: 0,
				scrollTopx:0,
				triggeredType:0
			};
		},
		props: {
			lazy: {
				//是否懒加载，两种模式一种懒加载（数据量在5000以下），虚拟dom（大数据模式）
				type: Boolean,
				String,
				default: false
			},
			pageSizes: {
				// 加载分页默认20
				type: Number,
				default: 20
			},
			data: {
				//数据
				type: Array,
				default () {
					return [];
				}
			},
			refresher: {
				//是否拥有下拉刷新
				type: Boolean,
				default: false
			},
			isBackTop: {
				//是否拥有回到顶部
				type: Boolean,
				default: false
			},
		},
		created() {
			this.list = JSON.parse(JSON.stringify(this.data));
			this.copyList = JSON.parse(JSON.stringify(this.list))
			this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
			this.getList();
		},
		mounted() {
			this.clientHeight = this.$refs.listtop.clientHeight
		},
		watch: {
			data() {
				// 数据变动之后初始化数据
				this.page = 0;
				this.height = 0;
				this.heightList = [0];
				this.list = JSON.parse(JSON.stringify(this.data));
				this.copyList = JSON.parse(JSON.stringify(this.list))
				this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
				this.getList();
			}
		},
		methods: {
			toTop() {
			     let that = this
				  let scrollTops = this.$refs.list.scrollTop
			      const duration = 1000 // duration 默认为 1000ms
			      const easing = (t) => t * (2 - t) // 缓动函数
			      let start = scrollTops // 起始位置
			      let end = 0 // 结束位置
			      let change = end - start // 变化量
			      let currentTime = 0 // 当前时间
			      // 滚动动画函数
			      const animateScroll = function () {
			        currentTime += 16 // 设定定时器暂停时间，16ms
			        let val = easing(currentTime / duration) * change + start
					that.$refs.list.scrollTop = val
			        if (currentTime < duration) {
			          setTimeout(animateScroll, 16)
			        }
			      }
			      animateScroll()
			 },
			touchstart(e){ //当手指触摸屏幕时候触发，即使已经有一个手指放在屏幕上也会触发。
			   if(this.height == 0 && this.refresher && this.triggeredType != 2){
				   let pas = e.changedTouches[0]
				   this.scrollClientY = pas.clientY
			   }
			},
			touchmove(e){ //当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间，调用preventDefault()事件可以阻止滚动
				if(this.height == 0 && this.refresher && this.triggeredType != 2){
					let pas = e.changedTouches[0]
					let y = (pas.clientY  -  this.scrollClientY)>0?(pas.clientY  -  this.scrollClientY):0
					this.scrollTop =  y > 150?150:y
					if(this.scrollTop >= 80){
						this.triggeredType = 1
					}else{
						this.triggeredType = 0
					}
				}
			},
			refreshers(){
				this.triggeredType = 3
				let that = this
				setTimeout(()=>{
					that.scrollTop = 0
				},800)
			},
			touchend(e){ //当手指从屏幕上离开的时候触发。
				if(this.height == 0 && this.refresher){
					if(this.scrollTop >= 80){
						this.triggeredType = 2
						this.scrollTop = 80
						this.$emit('refresher')
					}else{
						this.triggeredType = 0
						this.scrollTop = 0
					}
				}
			},
			//************  提供给外部的操作  ************** */
			getAllData() {
				return this.list;
			},
			deleteData(key, val) {
				//删除对应元素
				this.list2 = this.list2.filter(el => {
					return el[key] != val;
				});
				this.list = this.list.filter(el => {
					return el[key] != val;
				});
				this.listPage = this.listPage.map(ev => {
					return ev.filter(el => {
						return el[key] != val;
					});
				});
				this.copyList = JSON.parse(JSON.stringify(this.list))
				let that = this;
				this.$nextTick(() => {
					let data = that.$refs.list;
					if (
						data.clientHeight >= data.scrollHeight &&
						this.listPage.length > this.page + 1
					) {
						that.page++;
						that.heightList.push(data.scrollHeight);
						this.list2 = this.list2.concat(this.listPage[this.page]);
					}
				});
				this.$emit("operate", "deleteData");
			},
			getData(key, val) {
				//通过 key 查找起2对应val的数据
				return this.list.filter(el => {
					return el[key] == val;
				});
				this.$emit("operate", "getData");
			},
			setValData(key1, val1, key2, val2) {
				this.list2.forEach(el => {
					if (el[key1] == val1) {
						this.$set(el, key2, val2);
					}
				});
				this.list = this.list.forEach(el => {
					if (el[key1] == val1) {
						this.$set(el, key2, val2);
					}
					return el;
				});
				this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
				this.copyList = JSON.parse(JSON.stringify(this.list))
				this.$emit("operate", "setData");
			},
			setData(key, val) {
				//  改变全部数据，通过key来改变数据为val
				this.list2 = this.list2.map(el => {
					this.$set(el, key, val);
					return el;
				});
				this.list = this.list.map(el => {
					this.$set(el, key, val);
					return el;
				});
				this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
				this.copyList = JSON.parse(JSON.stringify(this.list))
				this.$emit("operate", "setData");
			},
			search(key, val, vague = true) {
				//  搜索操作，通过key来搜索val的数据
				this.page = 0;
				this.height = 0;
				this.heightList = [0];
				let vagues = vague ? true : false
				if (val) {
					this.list = JSON.parse(
						JSON.stringify(
							this.copyList.filter(el => {
								return vagues ? (el[key] && String(el[key]).indexOf(val) != -1) : (el[key] && el[
									key] == val)
							})
						)
					);
				} else {
					this.list = JSON.parse(JSON.stringify(this.copyList))
				}
				this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
				this.getList();
				this.$refs.list.scrollTop = 0;
				this.$emit("operate", "search");
			},
			// ***********  加载操作  ***************
			scroll(event) {
				let that = this;
				const container = this.$refs.list
				this.scrollTopx =  container.scrollTop
				if (this.lazy) {
					if (
						container.scrollTop + container.clientHeight + 5 >=
						container.scrollHeight
					) {
						if (this.listPage.length > this.page + 1) {
							// 滚动触底逻辑
							this.page++;
							// 往下一直加载
							this.list2 = this.list2.concat(this.listPage[this.page]);
						}
					}
				} else {
					if (
						container.scrollTop + container.clientHeight + 1 >=
						container.scrollHeight
					) {
						// 滚动触底逻辑
						if (this.listPage.length > this.page + 1 && !time) {
							time = setTimeout(() => {
								clearTimeout(time);
								time = null;
							}, 15);
							let scrollTop = container.scrollTop;
							this.heightList.push(container.scrollHeight);
							this.page++;
							// 往下一直加载
							this.list2 = this.list2.concat(this.listPage[this.page]);
							// 删除上一页数据
							if (this.list2.length > this.pageSizes * 2) {
								this.list2.splice(0, this.pageSizes);
								this.height = this.heightList[this.page - 1];
								setTimeout(() => {
									that.$refs.list.scrollTop = scrollTop + 5;
								}, 16);
							}
						}
					}
					if (
						container.scrollTop < this.height &&
						this.height > 0 &&
						this.page > 0
					) {
						// console.log("滚动到顶");
						this.page--;
						this.list2.splice(this.pageSizes, this.list2.length);
						this.list2 = [...this.listPage[this.page - 1], ...this.list2];
						this.height = this.heightList[this.page - 1];
						this.heightList.splice(this.page + 1, this.heightList.length);
					}
				}
			},
			splitArrayIntoGroups(array, groupSize) {
				const groups = [];
				for (let i = 0; i < array.length; i += groupSize) {
					groups.push(array.slice(i, i + groupSize));
				}
				return groups;
			},
			getList() {
				this.list2 = this.listPage[this.page];
				let that = this;
				this.$nextTick(() => {
					let data = that.$refs.list;
					if (
						data.clientHeight >= data.scrollHeight &&
						this.listPage.length > this.page + 1
					) {
						that.page++;
						that.heightList.push(data.scrollHeight);
						this.list2 = this.list2.concat(this.listPage[this.page]);
					}
				});
			}
		}
	};
</script>
<style lang="scss" scoped>
	.listtop {
		position: relative;
		height: 100%;
		width: 100%;
	}
	.refresherm{
		text-align: center;
		position: absolute;
		bottom: 0;
		line-height: 60px;
		left: 0;
		right: 0;
		color: #999;
	}

	.list {
		position: absolute;
		inset: 0;
		overflow: hidden;
		overflow-y: scroll;
	}
	
	._backtop{
		position: absolute;
		z-index: 1;
		right: 30rpx;
		bottom: 50px;
		.top{
			width: 60px;
			height: 30px;
			box-shadow: 0.3rem 0.3rem 0.6rem rgba(0, 0, 0, .3), -0.2rem -0.2rem 0.5rem rgba(255, 255, 255, .3);
			background: #fff;
			border-radius: 50%;
			padding: 15px 0;
			text-align: center;
			image{
				width: 30px;
				height: 30px;
			}
		}
		  /* 过渡类名 */
		  .fade-enter-active,
		  .fade-leave-active {
		    transition: opacity 0.7s;
		  }
		  .fade-enter,
		  .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
		    opacity: 0;
		  }
	}
</style>